home *** CD-ROM | disk | FTP | other *** search
/ Nebula 1 / Nebula One.iso / Mail / pine3.92 / pine / mailpart.c < prev    next >
C/C++ Source or Header  |  1996-03-14  |  64KB  |  2,473 lines

  1. #if !defined(lint) && !defined(DOS)
  2. static char rcsid[] = "$Id: mailpart.c,v 4.112 1996/03/15 07:13:42 hubert Exp $";
  3. #endif
  4. /*----------------------------------------------------------------------
  5.  
  6.             T H E    P I N E    M A I L   S Y S T E M
  7.  
  8.    Laurence Lundblade and Mike Seibel
  9.    Networks and Distributed Computing
  10.    Computing and Communications
  11.    University of Washington
  12.    Administration Builiding, AG-44
  13.    Seattle, Washington, 98195, USA
  14.    Internet: lgl@CAC.Washington.EDU
  15.              mikes@CAC.Washington.EDU
  16.  
  17.    Please address all bugs and comments to "pine-bugs@cac.washington.edu"
  18.  
  19.  
  20.    Pine and Pico are registered trademarks of the University of Washington.
  21.    No commercial use of these trademarks may be made without prior written
  22.    permission of the University of Washington.
  23.  
  24.    Pine, Pico, and Pilot software and its included text are Copyright
  25.    1989-1996 by the University of Washington.
  26.  
  27.    The full text of our legal notices is contained in the file called
  28.    CPYRIGHT, included with this distribution.
  29.  
  30.  
  31.    Pine is in part based on The Elm Mail System:
  32.     ***********************************************************************
  33.     *  The Elm Mail System  -  Revision: 2.13                             *
  34.     *                                                                     *
  35.     *             Copyright (c) 1986, 1987 Dave Taylor              *
  36.     *             Copyright (c) 1988, 1989 USENET Community Trust   *
  37.     ***********************************************************************
  38.  
  39.  
  40.   ----------------------------------------------------------------------*/
  41.  
  42. /*======================================================================
  43.      mailpart.c
  44.      The meat and pototoes of attachment processing here.
  45.  
  46.   ====*/
  47.  
  48. #include "headers.h"
  49.  
  50.  
  51. /*
  52.  * Information used to paint and maintain a line on the attachment
  53.  * screen.
  54.  */
  55. typedef struct atdisp_line {
  56.     char         *dstring;            /* alloc'd var value string  */
  57.     ATTACH_S         *attp;            /* actual attachment pointer */
  58.     struct atdisp_line *next, *prev;
  59. } ATDISP_S;
  60.  
  61.  
  62. /*
  63.  * struct defining attachment screen's current state
  64.  */
  65. typedef struct att_screen {
  66.     ATDISP_S  *current,
  67.           *top_line;
  68. } ATT_SCREEN_S;
  69. static ATT_SCREEN_S *att_screen;
  70.  
  71.  
  72. #define    next_attline(p)    ((p) ? (p)->next : NULL)
  73. #define    prev_attline(p)    ((p) ? (p)->prev : NULL)
  74.  
  75.  
  76. static struct key att_index_keys[] = 
  77.        {{"?","Help",KS_SCREENHELP},    {NULL,NULL,KS_NONE},
  78.     {"E","Exit Index",KS_EXITMODE},    {"V","[View]",KS_VIEW},
  79.     {"P","PrevAttch",KS_NONE},    {"N","NextAttch",KS_NONE},
  80.     {"-","PrevPage",KS_PREVPAGE},    {"Spc","NextPage",KS_NEXTPAGE},
  81.     {"A","AboutAttch",KS_NONE},    {"S","Save",KS_SAVE},
  82.     {"|","Pipe",KS_NONE},        {"W","WhereIs",KS_WHEREIS}};
  83. INST_KEY_MENU(att_index_keymenu, att_index_keys);
  84. #define    ATT_PIPE_KEY    10
  85.  
  86. /* used to keep track of detached MIME segments total length */
  87. static long    save_att_length;
  88.  
  89. /*
  90.  * Internal Prototypes
  91.  */
  92. int      display_attachment PROTO((long, ATTACH_S *));
  93. void      display_text_att PROTO((long, ATTACH_S *));
  94. void      display_msg_att PROTO((long, ATTACH_S *));
  95. void      display_abook_att PROTO((long, ATTACH_S *));
  96. void      display_attach_info PROTO((long, ATTACH_S *));
  97. void      run_viewer PROTO((char *, BODY *));
  98. ATDISP_S *new_attline PROTO((ATDISP_S **));
  99. void      free_attline PROTO((ATDISP_S **));
  100. ATDISP_S *first_attline PROTO((ATDISP_S *));
  101. void      attachment_screen_redrawer PROTO(());
  102. int      attachment_screen_updater PROTO((struct pine *, ATDISP_S *, \
  103.                        ATT_SCREEN_S *));
  104. int      save_att_percent PROTO(());
  105. int      df_trigger_cmp PROTO((long, char *));
  106. int      df_trigger_cmp_text PROTO((char *, char *));
  107. int      df_trigger_cmp_lwsp PROTO((char *, char *));
  108. int      df_trigger_cmp_start PROTO((char *, char *));
  109. int      df_static_trigger PROTO((BODY *));
  110. int      df_valid_command PROTO((char *));
  111. char     *dfilter PROTO((char *, STORE_S *, gf_io_t, filter_t *));
  112.  
  113.  
  114. #if defined(DOS) || defined(OS2)
  115. #define    READ_MODE    "rb"
  116. #define    WRITE_MODE    "wb"
  117. #else
  118. #define    READ_MODE    "r"
  119. #define    WRITE_MODE    "w"
  120. #endif
  121.  
  122.  
  123.  
  124. /*----------------------------------------------------------------------
  125.    Provide attachments in browser for selected action
  126.  
  127.   Args: ps -- pointer to pine structure
  128.     msgmap -- struct containing current message to display
  129.  
  130.   Result: 
  131.  
  132.   Handle painting and navigation of attachment index.  It would be nice
  133.   to someday handle message/rfc822 segments in a neat way (i.e., enable
  134.   forwarding, take address, etc.).
  135.  ----*/
  136. void
  137. attachment_screen(ps, msgmap)
  138.     struct pine *ps;
  139.     MSGNO_S     *msgmap;
  140. {
  141.     int          i, ch = 'x', orig_ch, done = 0, changes = 0, dline,
  142.           nl = 0, sl = 0, old_cols = -1, km_popped = 0;
  143.     long      msgno;
  144.     char     *p, *q;
  145.     ATTACH_S     *atmp;
  146.     ATDISP_S     *current = NULL, *ctmp = NULL;
  147.     ATT_SCREEN_S  screen;
  148.  
  149.     if(mn_total_cur(msgmap) > 1L){
  150.     q_status_message(SM_ORDER | SM_DING, 0, 3,
  151.              "Can only view one message's attachments at a time!");
  152.     return;
  153.     }
  154.     else if(ps->atmts && !(ps->atmts + 1)->description)
  155.       q_status_message1(SM_ASYNC, 0, 3,
  156.     "Message %s has only one part (the message body), and no attachments.",
  157.     long2string(mn_get_cur(msgmap)));
  158.  
  159.     /*
  160.      * find the longest number and size strings
  161.      */
  162.     for(atmp = ps->atmts; atmp && atmp->description; atmp++){
  163.     if((i = strlen(atmp->number)) > nl)
  164.       nl = i;
  165.  
  166.     if((i = strlen(atmp->size)) > sl)
  167.       sl = i;
  168.     }
  169.  
  170.     /*
  171.      * Then, allocate and initialize attachment line list...
  172.      */
  173.     for(atmp = ps->atmts; atmp && atmp->description; atmp++)
  174.       new_attline(¤t)->attp = atmp;
  175.  
  176.     screen.current     = screen.top_line = NULL;
  177.     msgno           = mn_m2raw(msgmap, mn_get_cur(msgmap));
  178.     ps->mangled_screen = 1;            /* build display */
  179.     ps->redrawer       = attachment_screen_redrawer;
  180.     att_screen           = &screen;
  181.     current           = first_attline(current);
  182.     if(ctmp = next_attline(current))
  183.       current = ctmp;
  184.  
  185.     while(!done){
  186.     if(km_popped){
  187.         km_popped--;
  188.         if(km_popped == 0){
  189.         clearfooter(ps);
  190.         ps->mangled_body = 1;
  191.         }
  192.     }
  193.  
  194.     if(ps->mangled_screen){
  195.         /*
  196.          * build/rebuild display lines
  197.          */
  198.         if(old_cols != ps->ttyo->screen_cols){
  199.         old_cols = ps->ttyo->screen_cols;
  200.         for(ctmp = first_attline(current);
  201.             ctmp && (atmp = ctmp->attp) && atmp->description;
  202.             ctmp = next_attline(ctmp)){
  203.             size_t len, dlen;
  204.  
  205.             if(ctmp->dstring)
  206.               fs_give((void **)&ctmp->dstring);
  207.  
  208.             len = max(80, ps->ttyo->screen_cols) * sizeof(char);
  209.             ctmp->dstring = (char *)fs_get(len + 1);
  210.             ctmp->dstring[len] = '\0';
  211.             memset(ctmp->dstring, ' ', len);
  212.             p = ctmp->dstring + 3;
  213.             for(i = 0; atmp->number[i]; i++)
  214.               *p++ = atmp->number[i];
  215.  
  216.             /* add 3 spaces, plus pad number, and right justify size */
  217.             p += 3 + nl - i + (sl - strlen(atmp->size));
  218.  
  219.             for(i = 0; atmp->size[i]; i++)
  220.               *p++ = atmp->size[i];
  221.  
  222.             p += 3;
  223.  
  224.             /* copy the type description */
  225.             q = type_desc(atmp->body->type, atmp->body->subtype,
  226.                   atmp->body->parameter, 1);
  227.             if((dlen = strlen(q)) > (i = len - (p - ctmp->dstring)))
  228.               dlen = i;
  229.  
  230.             strncpy(p, q, dlen);
  231.             p += dlen;
  232.  
  233.             /* provided there's room, copy the user's description */
  234.             if(len - (p - ctmp->dstring) > 8
  235.                && ((q = atmp->body->description)
  236.                || (atmp->body->type == TYPEMESSAGE
  237.                    && atmp->body->subtype
  238.                    && strucmp(atmp->body->subtype, "rfc822") == 0
  239.                    && atmp->body->contents.msg.env
  240.                    && (q=atmp->body->contents.msg.env->subject)))){
  241.             sstrcpy(&p, ", \"");
  242.             if((dlen=strlen(q)) > (i=len - (p - ctmp->dstring)))
  243.               dlen = i;
  244.  
  245.             strncpy(p, q, dlen);
  246.             *(p += dlen) = '\"';
  247.             }
  248.         }
  249.         }
  250.  
  251.         ps->mangled_header = 1;
  252.         ps->mangled_footer = 1;
  253.         ps->mangled_body   = 1;
  254.     }
  255.  
  256.     /*----------- Check for new mail -----------*/
  257.         if(new_mail(0, NM_TIMING(ch), 1) >= 0)
  258.           ps->mangled_header = 1;
  259.  
  260.     if(ps->mangled_header){
  261.         set_titlebar("ATTACHMENT INDEX", ps->mail_stream,
  262.              ps->context_current, ps->cur_folder, ps->msgmap, 1,
  263.              MessageNumber, 0, 0);
  264.         ps->mangled_header = 0;
  265.     }
  266.  
  267.     if(ps->mangled_screen){
  268.         ClearLine(1);
  269.         ps->mangled_screen = 0;
  270.     }
  271.  
  272.     dline = attachment_screen_updater(ps, current, &screen);
  273.  
  274.     /*---- This displays new mail notification, or errors ---*/
  275.     if(km_popped){
  276.         FOOTER_ROWS(ps) = 3;
  277.         mark_status_unknown();
  278.     }
  279.  
  280.         display_message(ch);
  281.     if(km_popped){
  282.         FOOTER_ROWS(ps) = 1;
  283.         mark_status_unknown();
  284.     }
  285.  
  286.         /*------ Read the command from the keyboard ----*/
  287.     if(ps->mangled_footer){
  288.         bitmap_t     bitmap;
  289.  
  290.         setbitmap(bitmap);
  291.         ps->mangled_footer = 0;
  292.         if(F_OFF(F_ENABLE_PIPE, ps))
  293.           clrbitn(ATT_PIPE_KEY, bitmap);    /* always clear for DOS */
  294.  
  295.         if(km_popped){
  296.         FOOTER_ROWS(ps) = 3;
  297.         clearfooter(ps);
  298.         }
  299.  
  300.         draw_keymenu(&att_index_keymenu, bitmap, ps->ttyo->screen_cols,
  301.              1-FOOTER_ROWS(ps), 0, FirstMenu,0);
  302.         if(km_popped){
  303.         FOOTER_ROWS(ps) = 1;
  304.         mark_keymenu_dirty();
  305.         }
  306.     }
  307.  
  308.     if(F_ON(F_SHOW_CURSOR, ps))
  309.       MoveCursor(dline, 0);
  310.     else
  311.       MoveCursor(max(0, ps->ttyo->screen_rows - FOOTER_ROWS(ps)), 0);
  312.  
  313. #ifdef    MOUSE
  314.     mouse_in_content(KEY_MOUSE, -1, -1, 0, 0); /* prime the handler */
  315.     register_mfunc(mouse_in_content, HEADER_ROWS(ps_global), 0,
  316.                ps_global->ttyo->screen_rows -(FOOTER_ROWS(ps_global)+1),
  317.                ps_global->ttyo->screen_cols);
  318. #endif
  319.     ch = orig_ch = read_command();
  320. #ifdef    MOUSE
  321.     clear_mfunc(mouse_in_content);
  322. #endif
  323.  
  324.         if(ch <= 0xff && isupper(ch))
  325.           ch = tolower(ch);
  326.  
  327.     if(km_popped)
  328.       switch(ch){
  329.         case NO_OP_IDLE:
  330.         case NO_OP_COMMAND: 
  331.         case KEY_RESIZE:
  332.         case ctrl('L'):
  333.           km_popped++;
  334.           break;
  335.         
  336.         default:
  337.           clearfooter(ps);
  338.           break;
  339.       }
  340.  
  341.     switch(ch){
  342.       case '?' :                /* help! */
  343.       case ctrl('G'):
  344.       case PF1 :
  345.         if(FOOTER_ROWS(ps) == 1 && km_popped == 0){
  346.         km_popped = 2;
  347.         ps->mangled_footer = 1;
  348.         break;
  349.         }
  350.  
  351.         helper(h_attachment_screen, "HELP FOR ATTACHMENT INDEX", 0);
  352.         ps->mangled_screen = 1;
  353.         break;
  354.  
  355.       case 'e' :                /* exit attachment screen */
  356.       case PF3 :
  357.         done++;
  358.         break;
  359.  
  360.       case 'n' :                /* next list element */
  361.       case '\t' :
  362.       case ctrl('F') :
  363.       case KEY_RIGHT :
  364.       case ctrl('N'):            /* down arrow */
  365.       case KEY_DOWN :
  366.       case PF6 :
  367.         ch = KEY_DOWN;
  368.         if(ctmp = next_attline(current))
  369.           current = ctmp;
  370.         else
  371.           q_status_message(SM_ORDER, 0, 1,
  372.                    "Already on last attachment");
  373.  
  374.         break;
  375.  
  376.       case 'p' :                /* previous list element */
  377.       case ctrl('B') :
  378.       case KEY_LEFT :
  379.       case ctrl('P') :            /* up arrow */
  380.       case KEY_UP :
  381.       case PF5 :
  382.         ch = KEY_UP;
  383.         if(ctmp = prev_attline(current))
  384.           current = ctmp;
  385.         else
  386.           q_status_message(SM_ORDER, 0, 1,
  387.                    "Already on first attachment");
  388.  
  389.         break;
  390.  
  391.       case '+' :                /* page forward */
  392.       case ' ' :
  393.       case ctrl('V') :
  394.       case PF8 :
  395.         ch = KEY_DOWN;
  396.         if(next_attline(current)){
  397.         while(dline++ < ps->ttyo->screen_rows - FOOTER_ROWS(ps))
  398.           if(ctmp = next_attline(current))
  399.             current = ctmp;
  400.           else
  401.             break;
  402.         }
  403.         else
  404.           q_status_message(SM_ORDER, 0, 1,
  405.                    "Already on last page of attachments");
  406.         
  407.  
  408.         break;
  409.  
  410.       case '-' :                /* page backward */
  411.       case ctrl('Y') :
  412.       case PF7 :
  413.         ch = KEY_UP;
  414.         if(prev_attline(current)){
  415.         while(dline-- > HEADER_ROWS(ps))
  416.           if(ctmp = prev_attline(current))
  417.             current = ctmp;
  418.           else
  419.             break;
  420.  
  421.         while(++dline < ps->ttyo->screen_rows - FOOTER_ROWS(ps))
  422.           if(ctmp = prev_attline(current))
  423.             current = ctmp;
  424.           else
  425.             break;
  426.         }
  427.         else
  428.           q_status_message(SM_ORDER, 0, 1,
  429.                    "Already on first page of attachments");
  430.  
  431.         break;
  432.  
  433. #ifdef MOUSE        
  434.       case KEY_MOUSE:
  435.         {
  436.         MOUSEPRESS mp;
  437.  
  438.         mouse_get_last (NULL, &mp);
  439.         mp.row -= HEADER_ROWS(ps);
  440.         ctmp = screen.top_line;
  441.         if (mp.doubleclick) {
  442.             display_attachment(msgno, current->attp);
  443.         }
  444.         else {
  445.             while (mp.row && ctmp != NULL) {
  446.             --mp.row;
  447.             ctmp = ctmp->next;
  448.             }
  449.             if (ctmp != NULL)
  450.               current = ctmp;
  451.         }
  452.         }
  453.         break;
  454. #endif
  455.  
  456.       case 'w' :                /* whereis */
  457.       case ctrl('W') :
  458.       case PF12 :
  459.         /*--- get string  ---*/
  460.         {int   rc, found = 0;
  461.          char *result = NULL, buf[64];
  462.          static char last[64], tmp[64];
  463.          HelpType help;
  464.  
  465.          ps->mangled_footer = 1;
  466.          buf[0] = '\0';
  467.          sprintf(tmp, "Word to find %s%.40s%s: ",
  468.              (last[0]) ? "[" : "",
  469.              (last[0]) ? last : "",
  470.              (last[0]) ? "]" : "");
  471.          help = NO_HELP;
  472.          while(1){
  473.          rc = optionally_enter(buf,-FOOTER_ROWS(ps),0,63,1,0,
  474.                      tmp,NULL,help,0);
  475.          if(rc == 3)
  476.            help = help == NO_HELP ? h_attach_index_whereis : NO_HELP;
  477.          else if(rc == 0 || rc == 1 || !buf[0]){
  478.              if(rc == 0 && !buf[0] && last[0])
  479.                strcpy(buf, last);
  480.  
  481.              break;
  482.          }
  483.          }
  484.  
  485.          if(rc == 0 && buf[0]){
  486.          ch   = KEY_DOWN;
  487.          ctmp = current;
  488.          while(ctmp = next_attline(ctmp))
  489.            if(srchstr(ctmp->dstring, buf)){
  490.                found++;
  491.                break;
  492.            }
  493.  
  494.          if(!found){
  495.              ctmp = first_attline(current);
  496.  
  497.              while(ctmp != current)
  498.                if(srchstr(ctmp->dstring, buf)){
  499.                found++;
  500.                break;
  501.                }
  502.                else
  503.              ctmp = next_attline(ctmp);
  504.          }
  505.          }
  506.          else
  507.            result = "WhereIs cancelled";
  508.  
  509.          if(found && ctmp){
  510.          strcpy(last, buf);
  511.          result  = "Word found";
  512.          current = ctmp;
  513.          }
  514.  
  515.          q_status_message(SM_ORDER, 0, 3,
  516.                   result ? result : "Word not found");
  517.         }
  518.  
  519.         break;
  520.  
  521.       case 'a' :
  522.       case PF9 :
  523.         display_attach_info(msgno, current->attp);
  524.         break;
  525.  
  526.       case ctrl('L'):            /* redraw */
  527.           case KEY_RESIZE:
  528.         ps->mangled_screen = 1;
  529.         break;
  530.  
  531.       case 'v':                /* View command */
  532.       case ctrl('M'):
  533.       case PF4 :
  534.         display_attachment(msgno, current->attp);
  535.         break;
  536.  
  537.       case 's':                /* Save command */
  538.       case PF10 :
  539.         save_attachment(-FOOTER_ROWS(ps), msgno, current->attp);
  540.         ps->mangled_footer = 1;
  541.         break;
  542.  
  543.       case '|':                /* Pipe command */
  544.       case PF11 :
  545.         if(F_ON(F_ENABLE_PIPE, ps)){
  546.         pipe_attachment(msgno, current->attp);
  547.         ps->mangled_footer = 1;
  548.         break;
  549.         }                    /* fall thru to complain */
  550.  
  551.       default:
  552.         bogus_command(orig_ch, F_ON(F_USE_FK, ps) ? "F1" : "?");
  553.  
  554.       case NO_OP_IDLE:            /* simple timeout */
  555.       case NO_OP_COMMAND:
  556.         break;
  557.     }
  558.     }
  559.  
  560.     for(current = first_attline(current); current;){    /* clean up */
  561.     ctmp = current->next;
  562.     free_attline(¤t);
  563.     current = ctmp;
  564.     }
  565.  
  566.     ps->mangled_screen = 1;
  567.     return;
  568. }
  569.  
  570.  
  571.  
  572. /*----------------------------------------------------------------------
  573.   allocate and attach a fresh attachment line struct
  574.  
  575.   Args: current -- display line to attache new struct to
  576.  
  577.   Returns: newly alloc'd attachment display line
  578.   ----*/
  579. ATDISP_S *
  580. new_attline(current)
  581.     ATDISP_S **current;
  582. {
  583.     ATDISP_S *p;
  584.  
  585.     p = (ATDISP_S *)fs_get(sizeof(ATDISP_S));
  586.     memset((void *)p, 0, sizeof(ATDISP_S));
  587.     if(current){
  588.     if(*current){
  589.         p->next         = (*current)->next;
  590.         (*current)->next = p;
  591.         p->prev         = *current;
  592.         if(p->next)
  593.           p->next->prev = p;
  594.     }
  595.  
  596.     *current = p;
  597.     }
  598.  
  599.     return(p);
  600. }
  601.  
  602.  
  603.  
  604. /*----------------------------------------------------------------------
  605.   Release system resources associated with attachment display line
  606.  
  607.   Args: p -- line to free
  608.  
  609.   Result: 
  610.   ----*/
  611. void
  612. free_attline(p)
  613.     ATDISP_S **p;
  614. {
  615.     if(p){
  616.     if((*p)->dstring)
  617.       fs_give((void **)&(*p)->dstring);
  618.  
  619.     fs_give((void **)p);
  620.     }
  621. }
  622.  
  623.  
  624.  
  625. /*----------------------------------------------------------------------
  626.   Manage display of the attachment screen menu body.
  627.  
  628.   Args: ps -- pine struct pointer
  629.     current -- currently selected display line
  630.     screen -- reference points for display painting
  631.  
  632.   Result: 
  633.  */
  634. int
  635. attachment_screen_updater(ps, current, screen)
  636.     struct pine  *ps;
  637.     ATDISP_S     *current;
  638.     ATT_SCREEN_S *screen;
  639. {
  640.     int          dline, return_line = HEADER_ROWS(ps);
  641.     ATDISP_S *top_line, *ctmp;
  642.  
  643.     /* calculate top line of display */
  644.     dline = 0;
  645.     ctmp = top_line = first_attline(current);
  646.     do
  647.       if(((dline++)%(ps->ttyo->screen_rows-HEADER_ROWS(ps)-FOOTER_ROWS(ps)))==0)
  648.     top_line = ctmp;
  649.     while(ctmp != current && (ctmp = next_attline(ctmp)));
  650.  
  651. #ifdef _WINDOWS
  652.     /* Don't know how to manage scroll bar for attachment screen yet. */
  653.     scroll_setrange (0L);
  654. #endif
  655.  
  656.     /* mangled body or new page, force redraw */
  657.     if(ps->mangled_body || screen->top_line != top_line)
  658.       screen->current = NULL;
  659.  
  660.     /* loop thru painting what's needed */
  661.     for(dline = 0, ctmp = top_line;
  662.     dline < ps->ttyo->screen_rows - FOOTER_ROWS(ps) - HEADER_ROWS(ps);
  663.     dline++, ctmp = next_attline(ctmp)){
  664.  
  665.     /*
  666.      * only fall thru painting if something needs painting...
  667.      */
  668.     if(!(!screen->current || ctmp == screen->current || ctmp == current))
  669.       continue;
  670.  
  671.     if(ctmp && ctmp->dstring){
  672.         char *p = tmp_20k_buf;
  673.         int   i, j, x = 0;
  674.         if(F_ON(F_FORCE_LOW_SPEED,ps) || ps->low_speed){
  675.         x = 2;
  676.         if(ctmp == current){
  677.             return_line = dline + HEADER_ROWS(ps);
  678.             PutLine0(dline + HEADER_ROWS(ps), 0, "->");
  679.         }
  680.         else
  681.           PutLine0(dline + HEADER_ROWS(ps), 0, "  ");
  682.  
  683.         /*
  684.          * Only paint lines if we have to...
  685.          */
  686.         if(screen->current)
  687.           continue;
  688.         }
  689.         else if(ctmp == current){
  690.         return_line = dline + HEADER_ROWS(ps);
  691.         StartInverse();
  692.         }
  693.  
  694.         /*
  695.          * Copy the value to a temp buffer expanding tabs, and
  696.          * making sure not to write beyond screen right...
  697.          */
  698.         for(i=0,j=x; ctmp->dstring[i] && j < ps->ttyo->screen_cols; i++){
  699.         if(ctmp->dstring[i] == ctrl('I')){
  700.             do
  701.               *p++ = ' ';
  702.             while(j < ps_global->ttyo->screen_cols && ((++j)&0x07));
  703.               
  704.         }
  705.         else{
  706.             *p++ = ctmp->dstring[i];
  707.             j++;
  708.         }
  709.         }
  710.  
  711.         *p = '\0';
  712.         PutLine0(dline + HEADER_ROWS(ps), x, tmp_20k_buf + x);
  713.  
  714.         if(ctmp == current
  715.            && !(F_ON(F_FORCE_LOW_SPEED,ps) || ps->low_speed))
  716.           EndInverse();
  717.     }
  718.     else
  719.       ClearLine(dline + HEADER_ROWS(ps));
  720.     }
  721.  
  722.     ps->mangled_body = 0;
  723.     screen->top_line = top_line;
  724.     screen->current  = current;
  725.     return(return_line);
  726. }
  727.  
  728.  
  729. /*----------------------------------------------------------------------
  730.   Redraw the attachment screen based on the global "att_screen" struct
  731.  
  732.   Args: none
  733.  
  734.   Result: 
  735.   ----*/
  736. void
  737. attachment_screen_redrawer()
  738. {
  739.     bitmap_t     bitmap;
  740.  
  741.     set_titlebar("ATTACHMENT INDEX", ps_global->mail_stream,
  742.          ps_global->context_current, ps_global->cur_folder,
  743.          ps_global->msgmap, 1, FolderName,0,0);
  744.  
  745.     ClearLine(1);
  746.  
  747.     ps_global->mangled_body = 1;
  748.     (void)attachment_screen_updater(ps_global,att_screen->current,att_screen);
  749.  
  750.     setbitmap(bitmap);
  751.     draw_keymenu(&att_index_keymenu, bitmap, ps_global->ttyo->screen_cols,
  752.          1-FOOTER_ROWS(ps_global), 0, FirstMenu,0);
  753. }
  754.  
  755.  
  756.  
  757. /*----------------------------------------------------------------------
  758.   Seek back from the given display line to the beginning of the list
  759.  
  760.   Args: p -- display linked list
  761.  
  762.   Result: 
  763.   ----*/
  764. ATDISP_S *
  765. first_attline(p)
  766.     ATDISP_S *p;
  767. {
  768.     while(p && p->prev)
  769.       p = p->prev;
  770.  
  771.     return(p);
  772. }
  773.  
  774.  
  775. int
  776. save_att_percent()
  777. {
  778.     int i = (int) (((pine_gets_bytes(0) + gf_bytes_piped()) * 100)
  779.                                / save_att_length);
  780.     return(min(i, 100));
  781. }
  782.  
  783.  
  784.  
  785. /*----------------------------------------------------------------------
  786.   Save the given attachment associated with the given message no
  787.  
  788.   Args: ps
  789.  
  790.   Result: 
  791.   ----*/
  792. void
  793. save_attachment(qline, msgno, a)
  794.      int       qline;
  795.      long      msgno;
  796.      ATTACH_S *a;
  797. {
  798.     char    filename[MAXPATH+1], full_filename[MAXPATH+1], *ill;
  799.     HelpType    help;
  800.     char       *l_string, prompt_buf[200];
  801.     int         r, is_text, over = 0, we_cancel = 0;
  802.     long        len;
  803.     PARAMETER  *param;
  804.     gf_io_t     pc;
  805.     STORE_S    *store;
  806.     char       *err;
  807.     struct variable *vars = ps_global->vars;
  808.     static ESCKEY_S att_save_opts[] = {
  809.     {ctrl('T'), 10, "^T", "To Files"},
  810.     {-1, 0, NULL, NULL},
  811.     {-1, 0, NULL, NULL},
  812.     {-1, 0, NULL, NULL}};
  813.  
  814.     is_text = a->body->type == TYPETEXT;
  815.  
  816.     /*-------  Figure out suggested file name ----*/
  817.     filename[0] = full_filename[0] = '\0';
  818.     for(param = a->body->parameter; param; param = param->next)
  819.       if(param->value && strucmp(param->attribute, "name") == 0){
  820.       strcpy(filename, param->value);
  821.       break;
  822.       }
  823.  
  824.     dprint(9, (debugfile, "save_attachment(name: %s)\n", filename));
  825.  
  826.     /*---------- Prompt the user for the file name -------------*/
  827.     r = 0;
  828. #if    !defined(DOS) && !defined(MAC) && !defined(OS2)
  829.     if(ps_global->VAR_DOWNLOAD_CMD && ps_global->VAR_DOWNLOAD_CMD[0]){
  830.     att_save_opts[++r].ch  = ctrl('V');
  831.     att_save_opts[r].rval  = 12;
  832.     att_save_opts[r].name  = "^V";
  833.     att_save_opts[r].label = "Downld Msg";
  834.     }
  835. #endif    /* !(DOS || MAC) */
  836.  
  837.     if(F_ON(F_ENABLE_TAB_COMPLETE,ps_global)){
  838.     att_save_opts[++r].ch  =  ctrl('I');
  839.     att_save_opts[r].rval  = 11;
  840.     att_save_opts[r].name  = "TAB";
  841.     att_save_opts[r].label = "Complete";
  842.     }
  843.  
  844.     att_save_opts[++r].ch = -1;
  845.  
  846.     help = NO_HELP;
  847.     while(1) {
  848.     sprintf(prompt_buf, "Copy attachment to file in %s directory: ",
  849.         F_ON(F_USE_CURRENT_DIR, ps_global) ? "current"
  850.         : VAR_OPER_DIR ? VAR_OPER_DIR : "home");
  851.     r = optionally_enter(filename, qline, 0, MAXPATH, 1, 0, prompt_buf,
  852.                  att_save_opts, help, 0);
  853.  
  854.         /*--- Help ----*/
  855.         if(r == 3) {
  856.             help = (help == NO_HELP) ? h_oe_export : NO_HELP;
  857.             continue;
  858.         }
  859.  
  860.     if(r == 10){                /* File Browser */
  861.         if(filename[0])
  862.           strcpy(full_filename, filename);
  863.         else if(F_ON(F_USE_CURRENT_DIR, ps_global))
  864.           (void) getcwd(full_filename, MAXPATH);
  865.         else if(VAR_OPER_DIR)
  866.           build_path(full_filename, VAR_OPER_DIR, filename);
  867.         else
  868.               build_path(full_filename, ps_global->home_dir, filename);
  869.  
  870.         r = file_lister("SAVE ATTACHMENT", full_filename, filename, TRUE,
  871.                 FB_SAVE);
  872.  
  873.         if(r == 1){
  874.         strcat(full_filename, "/");
  875.         strcat(full_filename, filename);
  876.         }
  877.         else
  878.           continue;
  879.     }
  880.     else if(r == 11){            /* File Name Completion */
  881.         char dir[MAXPATH], *fn;
  882.         int  l = MAXPATH;
  883.  
  884.         dir[0] = '\0';
  885.         if(*filename && (fn = last_cmpnt(filename))){
  886.         l -= fn - filename;
  887.         if(is_absolute_path(filename)){
  888.             strncpy(dir, filename, fn - filename);
  889.             dir[fn - filename] = '\0';
  890.         }
  891.         else{
  892.             char *p = NULL;
  893.             sprintf(full_filename, "%.*s", fn - filename, filename);
  894.             build_path(dir, F_ON(F_USE_CURRENT_DIR, ps_global)
  895.                        ? p = (char *) getcwd(NULL, MAXPATH)
  896.                        : VAR_OPER_DIR ? VAR_OPER_DIR
  897.                               : ps_global->home_dir,
  898.                    full_filename);
  899.             if(p)
  900.               free(p);
  901.         }
  902.         }
  903.         else{
  904.         fn = filename;
  905.         if(F_ON(F_USE_CURRENT_DIR, ps_global))
  906.           (void) getcwd(dir, MAXPATH);
  907.         else if(VAR_OPER_DIR)
  908.           strcpy(dir, VAR_OPER_DIR);
  909.         else
  910.           strcpy(dir, ps_global->home_dir);
  911.         }
  912.  
  913.         if(!pico_fncomplete(dir, fn, l - 1))
  914.           Writechar(BELL, 0);
  915.  
  916.         continue;
  917.     }
  918. #if    !defined(DOS) && !defined(MAC) && !defined(OS2)
  919.     else if(r == 12){            /* Download */
  920.         char     cmd[MAXPATH], *fp, *tfp;
  921.         int         next = 0;
  922.         PIPE_S  *syspipe;
  923.         gf_io_t  pc;
  924.  
  925.         if(ps_global->restricted){
  926.         q_status_message(SM_ORDER | SM_DING, 3, 3,
  927.                  "Download disallowed in restricted mode");
  928.         return;
  929.         }
  930.  
  931.         err = NULL;
  932.         tfp = temp_nam(NULL, "pd");
  933.         dprint(1, (debugfile, "Download attachment called!\n"));
  934.         if(store = so_get(FileStar, tfp, WRITE_ACCESS)){
  935.         gf_set_so_writec(&pc, store);
  936.  
  937.         if(save_att_length = 2 * a->body->size.bytes){
  938.             pine_gets_bytes(1);
  939.             sprintf(prompt_buf, "Saving to \"%.50s\"", full_filename);
  940.             we_cancel = busy_alarm(1, prompt_buf, save_att_percent, 1);
  941.         }
  942.  
  943.         if(err = detach(ps_global->mail_stream, msgno, a->body,
  944.                 a->number, &len, pc, NULL))
  945.           q_status_message2(SM_ORDER | SM_DING, 3, 5,
  946.                     "%s: Error writing attachment to \"%s\"",
  947.                     err, full_filename);
  948.  
  949.         /* cancel regardless, so it doesn't get in way of xfer */
  950.         cancel_busy_alarm(0);
  951.  
  952.         so_give(&store);        /* close file */
  953.  
  954.         if(!err){
  955.             build_updown_cmd(cmd, ps_global->VAR_DOWNLOAD_CMD_PREFIX,
  956.                      ps_global->VAR_DOWNLOAD_CMD, tfp);
  957.             if(syspipe = open_system_pipe(cmd, NULL, NULL,
  958.                           PIPE_USER | PIPE_RESET))
  959.               (void) close_system_pipe(&syspipe);
  960.             else
  961.               q_status_message(SM_ORDER | SM_DING, 3, 3,
  962.                        err = "Error running download command");
  963.         }
  964.  
  965.         unlink(tfp);
  966.         }
  967.         else
  968.           q_status_message(SM_ORDER | SM_DING, 3, 3,
  969.                    err = "Error building temp file for download");
  970.  
  971.         fs_give((void **)&tfp);
  972.         if(!err)
  973.           q_status_message1(SM_ORDER, 0, 4, "Part %s downloaded",
  974.                 a->number);
  975.                 
  976.  
  977.         return;
  978.     }
  979. #endif    /* !(DOS || MAC) */
  980.         else if(r == 1 || filename[0] == '\0') {
  981.             q_status_message(SM_ORDER, 0, 2, "Save attachment cancelled");
  982.             return;
  983.         }
  984.         else if(r == 4)
  985.           continue;
  986.  
  987.  
  988.         /* check out and expand file name. give possible error messages */
  989.     if(!full_filename[0])
  990.       strcpy(full_filename, filename);
  991.  
  992.         removing_trailing_white_space(full_filename);
  993.         removing_leading_white_space(full_filename);
  994.         if((ill = filter_filename(filename)) != NULL) {
  995. /* BUG: we should beep when the key's pressed rather than bitch later */
  996.             q_status_message1(SM_ORDER | SM_DING, 3, 3, "%s", ill);
  997.             continue;
  998.         }
  999.  
  1000. #if defined(DOS) || defined(OS2)
  1001.     if(is_absolute_path(full_filename)){
  1002.         fixpath(full_filename, MAXPATH);
  1003.     }
  1004. #else
  1005.     if(full_filename[0] == '~') {
  1006.         if(fnexpand(full_filename, sizeof(full_filename)) == NULL) {
  1007.         char *p = strindex(full_filename, '/');
  1008.         if(p != NULL)
  1009.           *p = '\0';
  1010.         q_status_message1(SM_ORDER | SM_DING, 3, 3,
  1011.                   "Error expanding file name: \"%s\" unknown user",
  1012.                   full_filename);
  1013.         continue;
  1014.         }
  1015.     }
  1016. #endif
  1017.  
  1018.     if(!is_absolute_path(full_filename)){
  1019.         if(F_ON(F_USE_CURRENT_DIR, ps_global))
  1020.           (void)strcpy(full_filename, filename);
  1021.         else if(VAR_OPER_DIR)
  1022.           build_path(full_filename, VAR_OPER_DIR, filename);
  1023.         else
  1024.           build_path(full_filename, ps_global->home_dir, filename);
  1025.     }
  1026.  
  1027.     break;        /* Must have got an OK file name */
  1028.     }
  1029.  
  1030.     if(ps_global->restricted) {
  1031.         q_status_message(SM_ORDER | SM_DING, 0, 4,
  1032.              "Pine demo can't save attachments");
  1033.         return;
  1034.     }
  1035.  
  1036.     if(VAR_OPER_DIR && !in_dir(VAR_OPER_DIR, full_filename)){
  1037.     q_status_message1(SM_ORDER, 0, 2, "Can't save to file outside of %s",
  1038.               VAR_OPER_DIR);
  1039.     return;
  1040.     }
  1041.      
  1042.  
  1043.     /*----------- Write the contents to the file -------------*/
  1044.     if(can_access(full_filename, ACCESS_EXISTS) == 0) {
  1045.     static ESCKEY_S access_opts[] = {
  1046.         {'o', 'o', "O", "Overwrite"},
  1047.         {'a', 'a', "A", "Append"},
  1048.         {-1, 0, NULL, NULL}};
  1049.  
  1050.         sprintf(prompt_buf,
  1051.         "File \"%s%s\" already exists.  Overwrite or append it ? ",
  1052.         ((r = strlen(full_filename)) > 20) ? "..." : "",
  1053.                 full_filename + ((r > 20) ? r - 20 : 0));
  1054.  
  1055.     switch(radio_buttons(prompt_buf, -FOOTER_ROWS(ps_global),
  1056.                  access_opts, 'a', 'x', NO_HELP, RB_NORM)){
  1057.       case 'o' :
  1058.         over = 1;
  1059.         if(unlink(full_filename) < 0){    /* BUG: breaks links */
  1060.         q_status_message2(SM_ORDER | SM_DING, 3, 5,
  1061.                   "Error deleting old %s: %s",
  1062.                   full_filename, error_description(errno));
  1063.         return;
  1064.         }
  1065.  
  1066.         break;
  1067.  
  1068.       case 'a' :
  1069.         over = -1;
  1070.         break;
  1071.  
  1072.       case 'x' :
  1073.       default :
  1074.             q_status_message(SM_ORDER, 0, 2, "Save of attachment cancelled");
  1075.             return;
  1076.     }
  1077.     }
  1078.  
  1079.     if((store = so_get(FileStar, full_filename, WRITE_ACCESS)) == NULL){
  1080.     q_status_message2(SM_ORDER | SM_DING, 3, 5,
  1081.               "Error opening destination %s: %s",
  1082.               full_filename, error_description(errno));
  1083.     return;
  1084.     }
  1085.  
  1086.     gf_set_so_writec(&pc, store);
  1087.  
  1088.     if(save_att_length = 2 * a->body->size.bytes){
  1089.     pine_gets_bytes(1);
  1090.     sprintf(prompt_buf, "Saving to \"%.50s\"", full_filename);
  1091.     we_cancel = busy_alarm(1, prompt_buf, save_att_percent, 1);
  1092.     }
  1093.  
  1094.     err = detach(ps_global->mail_stream,msgno,a->body,a->number,&len,pc,NULL);
  1095.  
  1096.     if(save_att_length && we_cancel)
  1097.       cancel_busy_alarm(0);
  1098.  
  1099.     if(!err){
  1100.         l_string = cpystr(byte_string(len));
  1101.         q_status_message8(SM_ORDER, 0, 4,
  1102.               "Part %s, %s%s %s to \"%s\"%s%s%s",
  1103.               a->number, 
  1104.               is_text
  1105.                 ? comatose(a->body->size.lines) : l_string,
  1106.               is_text ? " lines" : "",
  1107.               over==0 ? "written"
  1108.                   : over==1 ? "overwritten" : "appended",
  1109.               full_filename,
  1110.               (is_text || len == a->body->size.bytes)
  1111.                 ? "" : "(decoded from ",
  1112.                           (is_text || len == a->body->size.bytes)
  1113.                 ? "" : byte_string(a->body->size.bytes),
  1114.               is_text || len == a->body->size.bytes
  1115.                 ? "" : ")");
  1116.         fs_give((void **)&l_string);
  1117.     }
  1118.     else
  1119.       q_status_message2(SM_ORDER | SM_DING, 3, 5,
  1120.             "%s: Error writing attachment to \"%s\"",
  1121.                         err, full_filename);
  1122.     so_give(&store);
  1123. }
  1124.  
  1125.  
  1126. /*----------------------------------------------------------------------
  1127.   Unpack and display the given attachment associated with given message no.
  1128.  
  1129.   Args: msgno -- message no attachment is part of
  1130.     a -- attachment to display
  1131.  
  1132.   Returns: 0 on success, non-zero (and error message queued) otherwise
  1133.   ----*/        
  1134. int
  1135. display_attachment(msgno, a)
  1136.      long      msgno;
  1137.      ATTACH_S *a;
  1138. {
  1139.     char    *filename;
  1140.     STORE_S *store;
  1141.     gf_io_t  pc;
  1142.     char    *err;
  1143.     int      we_cancel =0;
  1144. #if !defined(DOS) && !defined(OS2)
  1145.     char     prefix[8];
  1146. #endif
  1147.  
  1148.     if(a->can_display == CD_DEFERRED){
  1149.     int use_viewer;
  1150.     a->can_display = mime_can_display(a->body->type, a->body->subtype,
  1151.                       a->body->parameter, &use_viewer)
  1152.               ? CD_GOFORIT : CD_NOCANDO;
  1153.     a->use_external_viewer = use_viewer;
  1154.     }
  1155.  
  1156.     /*------- Display the attachment -------*/
  1157.     if(a->can_display == CD_NOCANDO) {
  1158.         /*----- Can't display this type ------*/
  1159.     if(a->body->encoding < ENCOTHER)
  1160.       q_status_message3(SM_ORDER | SM_DING, 3, 5,
  1161.              "Don't know how to display %s%s%s attachments. Try Save.",
  1162.                 body_type_names(a->body->type),
  1163.                 a->body->subtype ? "/" : "",
  1164.                 a->body->subtype ? a->body->subtype :"");
  1165.     else
  1166.       q_status_message1(SM_ORDER | SM_DING, 3, 5,
  1167.                 "Don't know how to unpack \"%s\" encoding",
  1168.                 body_encodings[(a->body->encoding <= ENCMAX)
  1169.                          ? a->body->encoding : ENCOTHER]);
  1170.  
  1171.         return(1);
  1172.     }
  1173.     else if(!a->use_external_viewer){
  1174.     if(a->body->type == TYPETEXT)
  1175.       display_text_att(msgno, a);
  1176.     else if(a->body->type == TYPEMESSAGE)
  1177.       display_msg_att(msgno, a);
  1178.     else if(a->body->type == TYPEAPPLICATION
  1179.         && a->body->subtype
  1180.         && !strucmp(a->body->subtype, "DIRECTORY"))
  1181.       display_abook_att(msgno, a);
  1182.  
  1183.     ps_global->mangled_screen = 1;
  1184.     return(0);
  1185.     }
  1186.  
  1187.     /*------ Write the image to a temporary file ------*/
  1188. #if defined(DOS) || defined(OS2)
  1189.     filename = temp_nam(NULL, "im");
  1190. #else
  1191.     sprintf(prefix, "img-%.3s", (a->body->subtype) ? a->body->subtype : "unk");
  1192.     filename = temp_nam(NULL, prefix);
  1193. #endif
  1194.  
  1195.     if((store = so_get(FileStar, filename, WRITE_ACCESS)) == NULL){
  1196.         q_status_message2(SM_ORDER | SM_DING, 3, 5,
  1197.                           "Error \"%s\", Can't write file %s",
  1198.                           error_description(errno), filename);
  1199.         return(1);
  1200.     }
  1201.  
  1202.     if(save_att_length = 2 * a->body->size.bytes){
  1203.     char msg_buf[128];
  1204.  
  1205.     pine_gets_bytes(1);
  1206.     sprintf(msg_buf, "Decoding %s%.50s%s%s",
  1207.         a->description ? "\"" : "",
  1208.         a->description ? a->description : "attachment number ",
  1209.         a->description ? "" : a->number,
  1210.         a->description ? "\"" : "");
  1211.     we_cancel = busy_alarm(1, msg_buf, save_att_percent, 1);
  1212.     }
  1213.  
  1214.     gf_set_so_writec(&pc, store);
  1215.     err = detach(ps_global->mail_stream,msgno,a->body,a->number,NULL,pc,NULL);
  1216.  
  1217.     if(save_att_length && we_cancel)
  1218.       cancel_busy_alarm(0);
  1219.  
  1220.     if(err){
  1221.     q_status_message2(SM_ORDER | SM_DING, 3, 5,
  1222.              "%s: Error saving image to temp file %s", err, filename);
  1223.     return(1);
  1224.     }
  1225.  
  1226.     so_give(&store);
  1227.  
  1228.     /*----- Run the viewer process ----*/
  1229.     run_viewer(filename, a->body);
  1230.     fs_give((void **)&filename);
  1231.     return(0);
  1232. }
  1233.  
  1234.  
  1235. /*----------------------------------------------------------------------
  1236.    Fish the required command from mailcap and run it
  1237.  
  1238.   Args: image_file -- The name of the file to pass viewer
  1239.     body -- body struct containing type/subtype of part
  1240.  
  1241. A side effect may be that scrolltool is called as well if
  1242. exec_mailcap_cmd has any substantial output...
  1243.  ----*/
  1244. void
  1245. run_viewer(image_file, body)
  1246.      char *image_file;
  1247.      BODY *body;
  1248. {
  1249.     char *cmd            = NULL;
  1250.     int   needs_terminal = 0, we_cancel = 0;
  1251.  
  1252.     we_cancel = busy_alarm(1, "Displaying attachment", NULL, 1);
  1253.  
  1254.     if(cmd = mailcap_build_command(body, image_file, &needs_terminal)){
  1255.     if(we_cancel)
  1256.       cancel_busy_alarm(-1);
  1257.  
  1258.     exec_mailcap_cmd(cmd, image_file, needs_terminal);
  1259.     fs_give((void **)&cmd);
  1260.     }
  1261.     else{
  1262.     if(we_cancel)
  1263.       cancel_busy_alarm(-1);
  1264.  
  1265.     q_status_message1(SM_ORDER, 3, 4, "Cannot display %s attachment",
  1266.               type_desc(body->type, body->subtype,
  1267.                     body->parameter, 1));
  1268.     }
  1269. }
  1270.  
  1271.  
  1272.  
  1273. /*----------------------------------------------------------------------
  1274.   Detach and provide for browsing a text body part
  1275.  
  1276.   Args: msgno -- raw message number to get part from
  1277.      a -- attachment struct for the desired part
  1278.  
  1279.   Result: 
  1280.  ----*/
  1281. void
  1282. display_text_att(msgno, a)
  1283.     long      msgno;
  1284.     ATTACH_S *a;
  1285. {
  1286.     STORE_S        *store;
  1287.     gf_io_t         pc;
  1288.     SourceType        src = CharStar;
  1289.  
  1290.     clear_index_cache_ent(msgno);
  1291.  
  1292.     /* BUG, should check this return code */
  1293.     (void)mail_fetchstructure(ps_global->mail_stream, msgno, NULL);
  1294.  
  1295.     /* initialize a storage object */
  1296. #if    defined(DOS) && !defined(WIN32)
  1297.     if(a->body->size.bytes > MAX_MSG_INCORE
  1298.        || strcmp(ps_global->mail_stream->dtb->name, "nntp") == 0)
  1299.       src = FileStar;
  1300. #endif
  1301.  
  1302.     if(store = so_get(src, NULL, EDIT_ACCESS)){
  1303.     gf_set_so_writec(&pc, store);
  1304.     (void) decode_text(a, msgno, pc, QStatus, 1);
  1305.     scrolltool(so_text(store), "ATTACHED TEXT", AttachText, src, a);
  1306.     so_give(&store);    /* free resources associated with store */
  1307.     }
  1308.     else
  1309.       q_status_message(SM_ORDER | SM_DING, 3, 3,
  1310.                "Error allocating space for attachment.");
  1311. }
  1312.  
  1313.  
  1314. /*----------------------------------------------------------------------
  1315.   Detach and provide for browsing a body part of type "MESSAGE"
  1316.  
  1317.   Args: msgno -- message number to get partrom
  1318.      a -- attachment struct for the desired part
  1319.  
  1320.   Result: 
  1321.  ----*/
  1322. void
  1323. display_msg_att(msgno, a)
  1324.     long      msgno;
  1325.     ATTACH_S *a;
  1326. {
  1327.     STORE_S        *store;
  1328.     gf_io_t         pc;
  1329.     SourceType        src = CharStar;
  1330.  
  1331.     clear_index_cache_ent(msgno);
  1332.  
  1333.     /* BUG, should check this return code */
  1334.     (void)mail_fetchstructure(ps_global->mail_stream, msgno, NULL);
  1335.  
  1336.     /* initialize a storage object */
  1337. #if    defined(DOS) && !defined(WIN32)
  1338.     if(a->body->size.bytes > MAX_MSG_INCORE
  1339.        || strcmp(ps_global->mail_stream->dtb->name, "nntp") == 0)
  1340.       src = FileStar;
  1341. #endif
  1342.  
  1343.     if(!(store = so_get(src, NULL, EDIT_ACCESS))){
  1344.     q_status_message(SM_ORDER | SM_DING, 3, 3,
  1345.              "Error allocating space for message.");
  1346.     return;
  1347.     }
  1348.  
  1349.     gf_set_so_writec(&pc, store);
  1350.  
  1351.     if(a->body->subtype && strucmp(a->body->subtype, "rfc822") == 0){
  1352.     format_envelope(NULL, 0L, a->body->contents.msg.env, pc,
  1353.             FE_DEFAULT, NULL);
  1354.     gf_puts(NEWLINE, pc);
  1355.     if((a+1)->description && (a+1)->body && (a+1)->body->type == TYPETEXT){
  1356.         (void) decode_text(a+1, msgno, pc, QStatus, 1);
  1357.     }
  1358.     else{
  1359.         gf_puts("[Can't display ", pc);
  1360.         if((a+1)->description && (a+1)->body)
  1361.           gf_puts("first non-", pc);
  1362.         else
  1363.           gf_puts("missing ", pc);
  1364.  
  1365.         gf_puts("text segment]", pc);
  1366.     }
  1367.     }
  1368.     else if(a->body->subtype 
  1369.         && strucmp(a->body->subtype, "external-body") == 0) {
  1370.     gf_puts("This part is not included and can be fetched as follows:",pc);
  1371.     gf_puts(NEWLINE, pc);
  1372.     gf_puts(NEWLINE, pc);
  1373.     gf_puts(display_parameters(a->body->parameter), pc);
  1374.     }
  1375.     else
  1376.       (void) decode_text(a, msgno, pc, QStatus, 1);
  1377.  
  1378.     scrolltool(so_text(store), "ATTACHED MESSAGE", AttachText, src, a);
  1379.  
  1380.     so_give(&store);    /* free resources associated with store */
  1381. }
  1382.  
  1383.  
  1384. void
  1385. display_abook_att(msgno, a)
  1386.     long      msgno;
  1387.     ATTACH_S *a;
  1388. {
  1389.     STORE_S   *store;
  1390.     char     **lines, **ll, *defaulttype = NULL;
  1391.     PARAMETER *parm;
  1392.  
  1393.     lines = detach_abook_att(ps_global->mail_stream, msgno, a->body, a->number);
  1394.     if(!lines){
  1395.     q_status_message(SM_ORDER | SM_DING, 3, 3,
  1396.              "Error accessing attachment."); 
  1397.     return;
  1398.     }
  1399.  
  1400.     if(!(store = so_get(CharStar, NULL, EDIT_ACCESS))){
  1401.     if(lines)
  1402.       free_list(&lines);
  1403.  
  1404.     q_status_message(SM_ORDER | SM_DING, 3, 3,
  1405.              "Error allocating space for attachment."); 
  1406.     return;
  1407.     }
  1408.  
  1409.     for(parm = a->body->parameter; parm; parm = parm->next)
  1410.       if(!strucmp("DEFAULTTYPE", parm->attribute))
  1411.     break;
  1412.     
  1413.     if(parm)
  1414.       defaulttype = parm->value;
  1415.  
  1416.     for(ll = lines; ll && *ll && **ll; ll++){
  1417.     char *p;
  1418.  
  1419.     if(ll == lines)
  1420.       so_puts(store, "  ");
  1421.     else
  1422.       so_puts(store, "\n  ");
  1423.  
  1424.     p = (char *)rfc1522_decode((unsigned char *)tmp_20k_buf, *ll, NULL);
  1425.     if(p && *p == ':' && defaulttype){
  1426.         unsigned char buf[1000];
  1427.  
  1428.         so_puts(store, (char *)rfc1522_decode(buf, defaulttype, NULL));
  1429.     }
  1430.  
  1431.     so_puts(store, p);
  1432.     }
  1433.  
  1434.     so_puts(store, "\n\n(This is an address book entry which has been forwarded to you.\nYou may add it to your address book by exiting this screen and the\nattachment index screen, then using the \"TakeAddr\" command.  You will have\na chance to edit it before committing it to your address book.)\n");
  1435.  
  1436.     scrolltool(so_text(store), "ADDRESS BOOK ATTACHMENT", ViewAbookAtt,
  1437.            CharStar, NULL);
  1438.     so_give(&store);
  1439.  
  1440.     if(lines)
  1441.       free_list(&lines);
  1442. }
  1443.  
  1444.  
  1445.  
  1446. /*----------------------------------------------------------------------
  1447.   Display attachment information
  1448.  
  1449.   Args: msgno -- message number to get partrom
  1450.      a -- attachment struct for the desired part
  1451.  
  1452.   Result: a screen containing information about attachment:
  1453.       Type        :
  1454.       Subtype    :
  1455.       Parameters    :
  1456.         Comment    :
  1457.             FileName    :
  1458.       Encoded size    :
  1459.       Viewer    :
  1460.  ----*/
  1461. void
  1462. display_attach_info(msgno, a)
  1463.     long      msgno;
  1464.     ATTACH_S *a;
  1465. {
  1466.     int           i;
  1467.     STORE_S   *store;
  1468.     PARAMETER *parms;
  1469.  
  1470.     if(a->can_display == CD_DEFERRED){
  1471.     int use_viewer;
  1472.     a->can_display = mime_can_display(a->body->type, a->body->subtype,
  1473.                       a->body->parameter, &use_viewer)
  1474.               ? CD_GOFORIT : CD_NOCANDO;
  1475.     a->use_external_viewer = use_viewer;
  1476.     }
  1477.  
  1478.     if(!(store = so_get(CharStar, NULL, EDIT_ACCESS))){
  1479.     q_status_message(SM_ORDER | SM_DING, 3, 3,
  1480.              "Error allocating space for message.");
  1481.     return;
  1482.     }
  1483.  
  1484.     so_puts(store, "\nDetails about Attachment #");
  1485.     so_puts(store, a->number);
  1486.     so_puts(store, " :\n\n\n");
  1487.     so_puts(store, "\t\tType\t\t: ");
  1488.     so_puts(store, body_type_names(a->body->type));
  1489.     so_puts(store, "\n");
  1490.     so_puts(store, "\t\tSubtype\t\t: ");
  1491.     so_puts(store, a->body->subtype ? a->body->subtype : "Unknown");
  1492.     so_puts(store, "\n");
  1493.     so_puts(store, "\t\tEncoding\t: ");
  1494.     so_puts(store, a->body->encoding < ENCMAX
  1495.              ? body_encodings[a->body->encoding]
  1496.              : "Unknown");
  1497.     so_puts(store, "\n");
  1498.     if(a->body->parameter){
  1499.     so_puts(store, "\t\tParameters\t: ");
  1500.     for(i=0, parms=a->body->parameter; parms; parms = parms->next, i++){
  1501.         so_puts(store, parms->attribute);
  1502.         so_puts(store, " = ");
  1503.         so_puts(store, parms->value);
  1504.         so_puts(store, "\n");
  1505.         if(parms->next)
  1506.           so_puts(store, "\t\t\t\t  ");
  1507.     }
  1508.     }
  1509.  
  1510.     if(a->body->description){
  1511.     so_puts(store, "\t\tDescription\t: \"");
  1512.     so_puts(store, a->body->description);
  1513.     so_puts(store, "\"\n");
  1514.     /* BUG: Wrap description text if necessary */
  1515.     }
  1516.  
  1517.     so_puts(store, "\t\tApprox. Size\t: ");
  1518.     so_puts(store, comatose((a->body->encoding == ENCBASE64)
  1519.                   ? ((a->body->size.bytes * 3)/4)
  1520.                   : a->body->size.bytes));
  1521.     so_puts(store, " bytes\n");
  1522.     so_puts(store, "\t\tDisplay Method\t: ");
  1523.     if(a->can_display == CD_NOCANDO) {
  1524.     so_puts(store, "Can't, ");
  1525.     so_puts(store, (a->body->encoding < ENCOTHER)
  1526.              ? "Unknown Attachment Format"
  1527.              : "Unknown Encoding");
  1528.     }
  1529.     else if(!a->use_external_viewer){
  1530.     so_puts(store, "Pine's Internal Pager");
  1531.     }
  1532.     else{
  1533.     int   nt;
  1534.     char *cmd;
  1535.  
  1536.     if(cmd = mailcap_build_command(a->body, "<datafile>", &nt)){
  1537.         so_puts(store, "\"");
  1538.         so_puts(store, cmd);
  1539.         so_puts(store, "\"");
  1540.         fs_give((void **)&cmd);
  1541.     }
  1542.     }
  1543.  
  1544.     so_puts(store, "\n");
  1545.  
  1546.     scrolltool(so_text(store), "ABOUT ATTACHMENT", AttachText, CharStar, NULL);
  1547.     so_give(&store);    /* free resources associated with store */
  1548.     ps_global->mangled_screen = 1;
  1549. }
  1550.  
  1551.  
  1552.  
  1553. /*----------------------------------------------------------------------
  1554.  
  1555.   ----*/        
  1556. void
  1557. pipe_attachment(msgno, a)
  1558.      long      msgno;
  1559.      ATTACH_S *a;
  1560. {
  1561.     char    *err, *resultfilename = NULL, prompt[80];
  1562.     int      rc, flags, capture = 1, raw = 0, we_cancel = 0;
  1563.     PIPE_S  *syspipe;
  1564.     HelpType help;
  1565.     char     pipe_command[MAXPATH+1];
  1566.     static ESCKEY_S pipe_opt[] = {
  1567.     {0, 0, "", ""},
  1568.     {ctrl('W'), 10, "^W", NULL},
  1569.     {ctrl('Y'), 11, "^Y", NULL},
  1570.     {-1, 0, NULL, NULL}
  1571.     };
  1572.     
  1573.     if(ps_global->restricted){
  1574.     q_status_message(SM_ORDER | SM_DING, 0, 4,
  1575.              "Pine demo can't pipe attachments");
  1576.     return;
  1577.     }
  1578.  
  1579.     help = NO_HELP;
  1580.     pipe_command[0] = '\0';
  1581.     while(1){
  1582.     sprintf(prompt, "Pipe %sattachment %s to %s: ", raw ? "RAW " : "",
  1583.         a->number, capture ? "" : "(Free Output) ");
  1584.     pipe_opt[1].label = raw ? "DecodedData" : "Raw Data";
  1585.     pipe_opt[2].label = capture ? "Free Output" : "Capture Output";
  1586.     rc = optionally_enter(pipe_command, -FOOTER_ROWS(ps_global), 0,
  1587.                   MAXPATH, 1, 0, prompt, pipe_opt, help, 0);
  1588.     if(rc == -1){
  1589.         q_status_message(SM_ORDER | SM_DING, 3, 4,
  1590.                  "Internal problem encountered");
  1591.         break;
  1592.     }
  1593.     else if(rc == 10){
  1594.         raw = !raw;            /* flip raw text */
  1595.     }
  1596.     else if(rc == 11){
  1597.         capture = !capture;        /* flip capture output */
  1598.     }
  1599.     else if(rc == 0){
  1600.         if(pipe_command[0] == '\0'){
  1601.         q_status_message(SM_ORDER, 0, 2, "Pipe command cancelled");
  1602.         break;
  1603.         }
  1604.  
  1605.         flags = PIPE_USER | PIPE_WRITE | PIPE_STDERR;
  1606.         if(!capture){
  1607. #ifndef    _WINDOWS
  1608.         ClearScreen();
  1609.         fflush(stdout);
  1610.         clear_cursor_pos();
  1611.         ps_global->mangled_screen = 1;
  1612. #endif
  1613.         flags |= PIPE_RESET;
  1614.         }
  1615.  
  1616.         if(syspipe = open_system_pipe(pipe_command,
  1617.                    (flags&PIPE_RESET) ? NULL : &resultfilename,
  1618.                    NULL, flags)){
  1619.         gf_io_t  pc;        /* wire up a generic putchar */
  1620.         gf_set_writec(&pc, syspipe->ofilep, 0L, FileStar);
  1621.  
  1622.         /*------ Write the image to a temporary file ------*/
  1623.         if(raw){        /* pipe raw text */
  1624.             char      *contents;
  1625.             unsigned long len;
  1626.             gf_io_t    gc;
  1627.             SourceType src = CharStar;
  1628. #if    defined(DOS) && !defined(WIN32)
  1629.             char *tmpfile_name = NULL;
  1630. #endif
  1631.  
  1632.             err = NULL;
  1633. #if    defined(DOS) && !defined(WIN32)
  1634.             if(a->body->size.bytes > MAX_MSG_INCORE
  1635.                || !strcmp(ps_global->mail_stream->dtb->name,"nntp")){
  1636.             src = FileStar;
  1637.             if(!(tmpfile_name = temp_nam(NULL, "dt"))
  1638.                || !(append_file = fopen(tmpfile_name, "w+b"))){
  1639.                 if(tmpfile_name)
  1640.                   fs_give((void **)&tmpfile_name);
  1641.  
  1642.                 err = "Can't create temp file for pipe";
  1643.             }
  1644.             else
  1645.               mail_parameters(ps_global->mail_stream, SET_GETS,
  1646.                       (void *)dos_gets);
  1647.             }
  1648.             else
  1649.               mail_parameters(ps_global->mail_stream, SET_GETS,
  1650.                       (void *)NULL);
  1651. #endif    /* DOS */
  1652.  
  1653.             if(!capture)
  1654.               we_cancel = busy_alarm(1, NULL, NULL, 0);
  1655.  
  1656.             if(!err
  1657.                && !(contents = mail_fetchbody(ps_global->mail_stream,
  1658.                               msgno, a->number, &len)))
  1659.               err = "Can't access body part";
  1660.  
  1661.             if(!err){
  1662.             gf_set_readc(&gc,
  1663. #if    defined(DOS) && !defined(WIN32)
  1664.                      (src == FileStar) ? (void *)append_file :
  1665. #endif
  1666.                      contents, len, src);
  1667.             gf_filter_init();
  1668.             gf_link_filter(gf_nvtnl_local);
  1669.             err = gf_pipe(gc, pc);
  1670.             }
  1671.  
  1672. #if    defined(DOS) && !defined(WIN32)
  1673.             /*
  1674.              * free up file pointer, and delete tmpfile
  1675.              */
  1676.             if(src == FileStar){
  1677.             fclose(append_file);
  1678.             append_file = NULL;
  1679.             unlink(tmpfile_name);
  1680.             fs_give((void **)&tmpfile_name);
  1681.             mail_parameters(ps_global->mail_stream, SET_GETS,
  1682.                     (void *)NULL);
  1683.             mail_gc(ps_global->mail_stream, GC_TEXTS);
  1684.             }
  1685. #endif
  1686.             if(we_cancel)
  1687.               cancel_busy_alarm(0);
  1688.         }
  1689.         else{
  1690.             /* BUG: there's got to be a better way */
  1691.             if(!capture)
  1692.               ps_global->print = (PRINT_S *) 1;
  1693.  
  1694.             err = detach(ps_global->mail_stream, msgno, a->body,
  1695.                  a->number, (long *)NULL, pc, NULL);
  1696.             ps_global->print = (PRINT_S *) NULL;
  1697.         }
  1698.  
  1699.         if(err)
  1700.           q_status_message1(SM_ORDER | SM_DING, 3, 4,
  1701.                     "Error detaching for pipe: %s", err);
  1702.  
  1703.         (void) close_system_pipe(&syspipe);
  1704.         if(err)
  1705.           unlink(resultfilename);
  1706.         else
  1707.           display_output_file(resultfilename,"PIPE ATTACHMENT", NULL);
  1708.  
  1709.         fs_give((void **)&resultfilename);
  1710.         }
  1711.         else
  1712.           q_status_message(SM_ORDER | SM_DING, 3, 4,
  1713.                    "Error opening pipe");
  1714.  
  1715.         break;
  1716.     }
  1717.     else if(rc == 1){
  1718.         q_status_message(SM_ORDER, 0, 2,"Pipe cancelled");
  1719.         break;
  1720.     }
  1721.     else if(rc = 3)
  1722.       help = (help == NO_HELP) ? h_pipe_attach : NO_HELP;
  1723.     }
  1724. }
  1725.  
  1726.  
  1727. /*
  1728.  * We need to defined simple functions here for the piping and 
  1729.  * temporary storage object below.  We can use the filter.c functions
  1730.  * because they're already in use for the "putchar" function passed to
  1731.  * detach.
  1732.  */
  1733. static STORE_S *detach_so = NULL;
  1734.  
  1735. /*
  1736.  * The display filter is locally global because it's set in df_trigger_cmp
  1737.  * which sniffs at lines of the unencoded segment...
  1738.  */
  1739. static char    *display_filter;
  1740. static struct   triggers {
  1741.     int           (*cmp) PROTO((char *, char *));
  1742.     char        *text;
  1743.     char        *cmd;
  1744.     struct triggers *next;
  1745. } *df_trigger_list;
  1746. struct triggers *build_trigger_list PROTO(());
  1747. void         blast_trigger_list PROTO((struct triggers **));
  1748.  
  1749.  
  1750.  
  1751. int
  1752. detach_writec(c)
  1753.     int c;
  1754. {
  1755.     return(so_writec(c, detach_so));
  1756. }
  1757.  
  1758.  
  1759. /*----------------------------------------------------------------------
  1760.   detach the given body part using the given encoding
  1761.  
  1762.   Args: a bunch
  1763.  
  1764.   Returns: NULL on success, error message otherwise
  1765.   ----*/
  1766. char *
  1767. detach(stream, msg_no, body, part_no, len, pc, aux_filters)
  1768.      MAILSTREAM *stream;        /* c-client stream to use         */
  1769.      long        msg_no;        /* message number to deal with      */
  1770.      BODY       *body;            /* body pointer           */
  1771.      char       *part_no;        /* part number of message       */
  1772.      long       *len;            /* returns bytes read in this arg */
  1773.      gf_io_t     pc;            /* where to put it          */
  1774.      filter_t   *aux_filters;        /* null terminated array of filts */
  1775. {
  1776.     unsigned long  length, rv;
  1777.     int            we_cancel = 0;
  1778.     char          *status, *contents, *fcontents = NULL;
  1779.     gf_io_t        gc;
  1780.     SourceType     src = CharStar;
  1781.     static char    err_string[100];
  1782. #if    defined(DOS) && !defined(WIN32)
  1783.     char      *tmpfile_name = NULL;
  1784.     extern unsigned char  *xlate_to_codepage;
  1785. #endif
  1786.  
  1787.     err_string[0] = '\0';
  1788.  
  1789. #if    defined(DOS) && !defined(WIN32)
  1790.     if(body->size.bytes > MAX_MSG_INCORE || !strcmp(stream->dtb->name,"nntp")){
  1791.     src = FileStar;
  1792.     if(!(tmpfile_name = temp_nam(NULL, "dt"))
  1793.        || !(append_file = fopen(tmpfile_name, "w+b"))){
  1794.         if(tmpfile_name)
  1795.           fs_give((void **)&tmpfile_name);
  1796.  
  1797.         sprintf(err_string, "Can't detach!  Temp file %s",
  1798.             error_description(errno));
  1799.         return(err_string);
  1800.     }
  1801.  
  1802.     mail_parameters(stream, SET_GETS, (void *)dos_gets);
  1803.     }
  1804.     else
  1805.       mail_parameters(stream, SET_GETS, (void *)NULL);
  1806. #endif    /* DOS */
  1807.  
  1808.     if(!ps_global->print)
  1809.       we_cancel = busy_alarm(1, NULL, NULL, 0);
  1810.  
  1811.     /*
  1812.      * go grab the requested body part
  1813.      */
  1814.     contents = mail_fetchbody(stream, msg_no, part_no, &length);
  1815.     if(contents == NULL) {
  1816.     sprintf(err_string, "Unable to access body part %s", part_no);
  1817.     rv = 0L;
  1818.     goto fini;
  1819.     }
  1820.  
  1821.     rv = (length) ? length : 1L;
  1822.  
  1823.     gf_filter_init();            /* prepare to filter it! */
  1824.  
  1825.     switch(body->encoding) {        /* handle decoding */
  1826.       case ENC7BIT:
  1827.       case ENC8BIT:
  1828.       case ENCBINARY:
  1829.         break;
  1830.  
  1831.       case ENCBASE64:
  1832.     gf_link_filter(gf_b64_binary);
  1833.         break;
  1834.  
  1835.       case ENCQUOTEDPRINTABLE:
  1836.     gf_link_filter(gf_qp_8bit);
  1837.         break;
  1838.  
  1839.       case ENCOTHER:
  1840.       default:
  1841.     dprint(1, (debugfile, "detach: unknown CTE: \"%s\" (%d)\n",
  1842.            (body->encoding <= ENCMAX) ? body_encodings[body->encoding]
  1843.                           : "BEYOND-KNOWN-TYPES",
  1844.            body->encoding));
  1845.     break;
  1846.     }
  1847.  
  1848.     /*
  1849.      * If we're detaching a text segment and there are user-defined
  1850.      * filters and there are text triggers to look for, install filter
  1851.      * to let us look at each line...
  1852.      */
  1853.     display_filter = NULL;
  1854.     if(body->type == TYPETEXT && ps_global->VAR_DISPLAY_FILTERS){
  1855.     /* check for "static" triggers (i.e., none or CHARSET) */
  1856.     if(!df_static_trigger(body)
  1857.        && (df_trigger_list = build_trigger_list())){
  1858.         /* else look for matching text trigger */
  1859.         gf_line_test_opt(df_trigger_cmp);
  1860.         gf_link_filter(gf_line_test);
  1861.     }
  1862.     }
  1863.     else
  1864.       /* add aux filters if we're not going to MIME decode into a temporary
  1865.        * storage object, otherwise we pass the aux_filters on to gf_filter
  1866.        * below so it can pass what comes out of the external filter command
  1867.        * thru the rest of the filters...
  1868.        */
  1869.       while(aux_filters && *aux_filters)    /* apply provided filters */
  1870.     gf_link_filter(*aux_filters++);
  1871.  
  1872.     /*
  1873.      * Following canonical model, after decoding convert newlines from
  1874.      * crlf to local convention.
  1875.      */
  1876.     if(body->type == TYPETEXT || (body->type == TYPEMESSAGE
  1877.                   && !strucmp(body->subtype, "rfc822"))){
  1878.     gf_link_filter(gf_nvtnl_local);
  1879. #if    defined(DOS) && !defined(WIN32)
  1880.     /*
  1881.      * When detaching a text part AND it's US-ASCII OR it
  1882.      * matches what the user's defined as our charset,
  1883.      * translate it...
  1884.      */
  1885.     if(mime_can_display(body->type, body->subtype, body->parameter,
  1886.                                 (int *)NULL)
  1887.        && xlate_to_codepage){
  1888.         gf_translate_opt(xlate_to_codepage, 256);
  1889.         gf_link_filter(gf_translate);
  1890.     }
  1891. #endif
  1892.     }
  1893.  
  1894. #ifdef    LATER
  1895.     dprint(9, (debugfile, "Attachment lengths: %ld (decoded) %lud (encoded)\n",
  1896.                *decoded_len, length));
  1897. #endif
  1898.  
  1899.     gf_set_readc(&gc,
  1900. #if    defined(DOS) && !defined(WIN32)
  1901.          /*
  1902.           * If we fetched this to a file, make sure we set
  1903.           * up the storage object's getchar function accordingly...
  1904.           */
  1905.          (src == FileStar) ? (void *)append_file :
  1906. #endif
  1907.          contents, length, src);
  1908.  
  1909.     /*
  1910.      * If we're detaching a text segment and a user-defined filter may
  1911.      * need to be invoked later (see below), decode the segment into
  1912.      * a temporary storage object...
  1913.      */
  1914.     if(body->type == TYPETEXT && ps_global->VAR_DISPLAY_FILTERS
  1915.        && !(detach_so = so_get(src, NULL, EDIT_ACCESS)))
  1916.       strcpy(err_string,
  1917.        "Formatting error: no space to make copy, no display filters used");
  1918.  
  1919.     if(status = gf_pipe(gc, detach_so ? detach_writec : pc)) {
  1920.     sprintf(err_string, "Formatting error: %s", status);
  1921.         rv = 0L;
  1922.     }
  1923.  
  1924.     /*
  1925.      * If we wrote to a temporary area, there MAY be a user-defined
  1926.      * filter to invoke.  Filter it it (or not if no trigger match)
  1927.      * *AND* send the output thru any auxiliary filters, destroy the
  1928.      * temporary object and be done with it...
  1929.      */
  1930.     if(detach_so){
  1931.     if(!err_string[0] && display_filter && *display_filter){
  1932.         filter_t *p, *aux = NULL;
  1933.  
  1934.         if(aux_filters && *aux_filters){
  1935.         /* insert NL conversion filters around remaining aux_filters
  1936.          * so they're not tripped up by local NL convention
  1937.          */
  1938.         for(p = aux_filters; *p; p++)    /* count aux_filters */
  1939.           ;
  1940.  
  1941.         p = aux = fs_get(((p - aux_filters) + 3) * sizeof(filter_t));
  1942.         *p++ = gf_local_nvtnl;
  1943.         for(; *aux_filters; p++, aux_filters++)
  1944.           *p = *aux_filters;
  1945.  
  1946.         *p++ = gf_nvtnl_local;
  1947.         *p   = NULL;
  1948.         }
  1949.  
  1950.         if(status = dfilter(display_filter, detach_so, pc, aux)){
  1951.         sprintf(err_string, "Formatting error: %s", status);
  1952.         rv = 0L;
  1953.         }
  1954.  
  1955.         if(aux)
  1956.           fs_give((void **)&aux);
  1957.     }
  1958.     else{                    /*  just copy it, then */
  1959.         gf_set_so_readc(&gc, detach_so);
  1960.         so_seek(detach_so, 0L, 0);
  1961.         gf_filter_init();
  1962.         if(aux_filters && *aux_filters){
  1963.         /* if other filters are involved, correct for
  1964.          * newlines on either side of the pipe...
  1965.          */
  1966.         gf_link_filter(gf_local_nvtnl);
  1967.         while(*aux_filters)
  1968.           gf_link_filter(*aux_filters++);
  1969.  
  1970.         gf_link_filter(gf_nvtnl_local);
  1971.         }
  1972.  
  1973.         if(status = gf_pipe(gc, pc)){    /* Second pass, sheesh */
  1974.         sprintf(err_string, "Formatting error: %s", status);
  1975.         rv = 0L;
  1976.         }
  1977.     }
  1978.  
  1979.     so_give(&detach_so);            /* blast temp copy */
  1980.     }
  1981.  
  1982.   fini :
  1983.  
  1984. #if    defined(DOS) && !defined(WIN32)
  1985.     /*
  1986.      * free up file pointer, and delete tmpfile opened for
  1987.      * dos_gets.  Again, we can't use DOS' tmpfile() as it writes root
  1988.      * sheesh.
  1989.      */
  1990.     if(src == FileStar){
  1991.     fclose(append_file);
  1992.     append_file = NULL;
  1993.     unlink(tmpfile_name);
  1994.     fs_give((void **)&tmpfile_name);
  1995.     mail_parameters(stream, SET_GETS, (void *)NULL);
  1996.     mail_gc(stream, GC_TEXTS);
  1997.     }
  1998. #endif
  1999.  
  2000.     if(!ps_global->print && we_cancel)
  2001.       cancel_busy_alarm(0);
  2002.  
  2003.     if (len)
  2004.       *len = rv;
  2005.  
  2006.     if(df_trigger_list)
  2007.       blast_trigger_list(&df_trigger_list);
  2008.  
  2009.     return((err_string[0] == '\0') ? NULL : err_string);
  2010. }
  2011.  
  2012.  
  2013. /*
  2014.  * df_static_trigger - look thru the display filter list and set the
  2015.  *               filter to any triggers that don't require scanning
  2016.  *               the message segment.
  2017.  */
  2018. int
  2019. df_static_trigger(body)
  2020.     BODY *body;
  2021. {
  2022.     char **l;
  2023.     int    success = 0;
  2024.     char  *test = NULL, *cmd = NULL;
  2025.  
  2026.     for(l = ps_global->VAR_DISPLAY_FILTERS ; l && *l && !success; l++){
  2027.     get_pair(*l, &test, &cmd, 1);
  2028.  
  2029.     if(test){
  2030.         if(!*test){                /* null test means always! */
  2031.         success = 1;
  2032.         }
  2033.         else if(struncmp(test, "_CHARSET(", 9) == 0){
  2034.         PARAMETER *params = body->parameter;
  2035.         char *p = strrindex(test, ')');
  2036.         if(p){
  2037.             *p = '\0';
  2038.             while(params && strucmp(params->attribute, "charset"))
  2039.               params = params->next;
  2040.  
  2041.             success = !strucmp(test + 9,
  2042.                        params ? params->value : "us-ascii");
  2043.         }
  2044.         else
  2045.           dprint(1, (debugfile,
  2046.                  "filter trigger: malformed test: %s\n", test));
  2047.         }
  2048.  
  2049.         fs_give((void **)&test);        /* clean up test */
  2050.     }
  2051.     /* else empty list value */
  2052.  
  2053.     /*
  2054.      * make sure cmd exists!  NOTE: success overloaded
  2055.      */
  2056.     if(!(success && df_valid_command(cmd))){
  2057.         success = 0;
  2058.         if(cmd)
  2059.           fs_give((void **)&cmd);
  2060.         else
  2061.           dprint(5, (debugfile, "filter trigger: EMPTY command!\n"));
  2062.     }
  2063.     }
  2064.  
  2065.     return((display_filter = cmd) != NULL);
  2066. }
  2067.  
  2068.  
  2069. /*
  2070.  * build_trigger_list - return possible triggers in a list of triggers 
  2071.  *            structs
  2072.  */
  2073. struct triggers *
  2074. build_trigger_list()
  2075. {
  2076.     struct triggers  *tp = NULL, **trailp;
  2077.     char        **l, *test, *str, *ep, *cmd = NULL;
  2078.     int              i;
  2079.  
  2080.     trailp = &tp;
  2081.     for(l = ps_global->VAR_DISPLAY_FILTERS ; l && *l; l++){
  2082.     get_pair(*l, &test, &cmd, 1);
  2083.     if(test && df_valid_command(cmd)){
  2084.         *trailp      = (struct triggers *)fs_get(sizeof(struct triggers));
  2085.         (*trailp)->cmp = df_trigger_cmp_text;
  2086.         str           = test;
  2087.         if(*test == '_' && (i = strlen(test)) > 10
  2088.            && *(ep = &test[i-1]) == '_' && *--ep == ')'){
  2089.         if(struncmp(test, "_CHARSET(", 9) == 0){
  2090.             fs_give((void **)&test);
  2091.             fs_give((void **)&cmd);
  2092.             fs_give((void **)trailp);
  2093.             continue;
  2094.         }
  2095.  
  2096.         if(strncmp(test+1, "LEADING(", 8) == 0){
  2097.             (*trailp)->cmp = df_trigger_cmp_lwsp;
  2098.             *ep           = '\0';
  2099.             str           = cpystr(test+9);
  2100.             fs_give((void **)&test);
  2101.         }
  2102.         else if(strncmp(test+1, "BEGINNING(", 10) == 0){
  2103.             (*trailp)->cmp = df_trigger_cmp_start;
  2104.             *ep           = '\0';
  2105.             str           = cpystr(test+11);
  2106.             fs_give((void **)&test);
  2107.         }
  2108.         }
  2109.  
  2110.         (*trailp)->text = str;
  2111.         (*trailp)->cmd = cmd;
  2112.         *(trailp = &(*trailp)->next) = NULL;
  2113.     }
  2114.     else{
  2115.         fs_give((void **)&test);
  2116.         fs_give((void **)&cmd);
  2117.     }
  2118.     }
  2119.  
  2120.     return(tp);
  2121. }
  2122.  
  2123.  
  2124. /*
  2125.  * blast_trigger_list - zot any list of triggers we've been using 
  2126.  */
  2127. void
  2128. blast_trigger_list(tlist)
  2129.     struct triggers **tlist;
  2130. {
  2131.     if((*tlist)->next)
  2132.       blast_trigger_list(&(*tlist)->next);
  2133.  
  2134.     fs_give((void **)&(*tlist)->text);
  2135.     fs_give((void **)&(*tlist)->cmd);
  2136.     fs_give((void **)tlist);
  2137. }
  2138.  
  2139.  
  2140. /*
  2141.  * df_trigger_cmp - compare the line passed us with the list of defined
  2142.  *            display filter triggers
  2143.  */
  2144. int
  2145. df_trigger_cmp(n, s)
  2146.     long  n;
  2147.     char *s;
  2148. {
  2149.     register struct triggers *tp;
  2150.     int                  result;
  2151.  
  2152.     if(!display_filter)                /* already found? */
  2153.       for(tp = df_trigger_list; tp; tp = tp->next)
  2154.     if(tp->cmp){
  2155.         if((result = (*tp->cmp)(s, tp->text)) < 0)
  2156.           tp->cmp = NULL;
  2157.         else if(result > 0)
  2158.           return((display_filter = tp->cmd) != NULL);
  2159.     }
  2160.  
  2161.     return(0);
  2162. }
  2163.  
  2164.  
  2165. /*
  2166.  * df_trigger_cmp_text - return 1 if s1 is in s2
  2167.  */
  2168. int
  2169. df_trigger_cmp_text(s1, s2)
  2170.     char *s1, *s2;
  2171. {
  2172.     return(strstr(s1, s2) != NULL);
  2173. }
  2174.  
  2175.  
  2176. /*
  2177.  * df_trigger_cmp_lwsp - compare the line passed us with the list of defined
  2178.  *                 display filter triggers. returns:
  2179.  *
  2180.  *            0  if we don't know yet
  2181.  *            1  if we match
  2182.  *           -1  if we clearly don't match
  2183.  */
  2184. int
  2185. df_trigger_cmp_lwsp(s1, s2)
  2186.     char *s1, *s2;
  2187. {
  2188.     while(*s1 && isspace(*s1))
  2189.       s1++;
  2190.  
  2191.     return((*s1) ? (!strncmp(s1, s2, strlen(s2)) ? 1 : -1) : 0);
  2192. }
  2193.  
  2194.  
  2195. /*
  2196.  * df_trigger_cmp_start - return 1 if first strlen(s2) chars start s1
  2197.  */
  2198. int
  2199. df_trigger_cmp_start(s1, s2)
  2200.     char *s1, *s2;
  2201. {
  2202.     return(!strncmp(s1, s2, strlen(s2)));
  2203. }
  2204.  
  2205.  
  2206. /*
  2207.  * df_valid_command - make sure argv[0] of command really exists
  2208.  */
  2209. int
  2210. df_valid_command(cmd)
  2211.     char *cmd;
  2212. {
  2213.     int   rv = 0;
  2214.     char *p;
  2215.  
  2216.     if(p = strchr(cmd, ' '))
  2217.       *p = '\0';
  2218.  
  2219.     rv = is_absolute_path(cmd) && can_access(cmd, EXECUTE_ACCESS) == 0;
  2220.     if(p)
  2221.       *p = ' ';
  2222.  
  2223.     return(rv);
  2224. }
  2225.  
  2226.  
  2227.  
  2228. /*
  2229.  * dfilter - pipe the data from the given storage object thru the
  2230.  *         global display filter and into whatever the putchar's
  2231.  *         function points to.
  2232.  */
  2233. char *
  2234. dfilter(rawcmd, input_so, output_pc, aux_filters)
  2235.     char    *rawcmd;
  2236.     STORE_S *input_so;
  2237.     gf_io_t  output_pc;
  2238.     filter_t   *aux_filters;
  2239. {
  2240.     char *status = NULL, *cmd, *resultf = NULL, *tmpfile = NULL;
  2241.     int   key = 0;
  2242.  
  2243.     if(cmd = expand_filter_tokens(rawcmd, NULL, &tmpfile, &resultf, &key)){
  2244.     suspend_busy_alarm();
  2245. #ifndef    _WINDOWS
  2246.     ps_global->mangled_screen = 1;
  2247.     ClearScreen();
  2248.     fflush(stdout);
  2249. #endif
  2250.  
  2251.     /*
  2252.      * If it was requested that the interaction take place via
  2253.      * a tmpfile, fill it with text from our input_so, and let
  2254.      * system_pipe handle the rest...
  2255.      */
  2256.     if(tmpfile){
  2257.         PIPE_S      *filter_pipe;
  2258.         FILE      *fp;
  2259.         gf_io_t       gc, pc;
  2260.  
  2261.         /* write the tmp file */
  2262.         so_seek(input_so, 0L, 0);
  2263.         if(fp = fopen(tmpfile, WRITE_MODE)){
  2264.         /* copy input to tmp file */
  2265.         gf_set_so_readc(&gc, input_so);
  2266.         gf_set_writec(&pc, fp, 0L, FileStar);
  2267.         gf_filter_init();
  2268.         if(!(status = gf_pipe(gc, pc))){
  2269.             fclose(fp);        /* close descriptor */
  2270.             if(filter_pipe = open_system_pipe(cmd, NULL, NULL,
  2271.                               PIPE_USER | PIPE_RESET)){
  2272.             (void) close_system_pipe(&filter_pipe);
  2273.  
  2274.             /* pull result out of tmp file */
  2275.             if(fp = fopen(tmpfile, READ_MODE)){
  2276.                 gf_set_readc(&gc, fp, 0L, FileStar);
  2277.                 gf_filter_init();
  2278.                 while(aux_filters && *aux_filters)
  2279.                   gf_link_filter(*aux_filters++);
  2280.  
  2281.                 status = gf_pipe(gc, output_pc);
  2282.             }
  2283.             else
  2284.               status = "Can't read result of display filter";
  2285.             }
  2286.             else
  2287.               status = "Can't open pipe for display filter";
  2288.         }
  2289.  
  2290.         unlink(tmpfile);
  2291.         }
  2292.         else
  2293.           status = "Can't open display filter tmp file";
  2294.     }
  2295.     else if(status = gf_filter(cmd, key ? filter_session_key() : NULL,
  2296.                    input_so, output_pc, aux_filters)){
  2297.         int ch;
  2298.  
  2299.         fprintf(stdout,"\r\n%s  Hit return to continue.", status);
  2300.         fflush(stdout);
  2301.         while((ch = read_char(300)) != ctrl('M') && ch != NO_OP_IDLE)
  2302.           putchar(BELL);
  2303.     }
  2304.  
  2305.     if(resultf){
  2306.         if(name_file_size(resultf) > 0L)
  2307.           display_output_file(resultf, "Filter", NULL);
  2308.  
  2309.         fs_give((void **)&resultf);
  2310.     }
  2311.  
  2312.     resume_busy_alarm();
  2313. #ifndef    _WINDOWS
  2314.     ClearScreen();
  2315. #endif
  2316.     fs_give((void **)&cmd);
  2317.     }
  2318.  
  2319.     return(status);
  2320. }
  2321.  
  2322.  
  2323. /*
  2324.  * expand_filter_tokens - return an alloc'd string with any special tokens
  2325.  *              in the given filter expanded, NULL otherwise.
  2326.  */
  2327. char *
  2328. expand_filter_tokens(filter, env, tmpf, resultf, key)
  2329.     char      *filter;
  2330.     ENVELOPE  *env;
  2331.     char     **tmpf, **resultf;
  2332.     int       *key;
  2333. {
  2334.     char *cmd = NULL, *rlp = NULL, *tfp = NULL, *rfp = NULL,
  2335.      *dfp = NULL, *skp = NULL;
  2336.  
  2337.     /*
  2338.      * Here's where we scan the filter replacing:
  2339.      * _TMPFILE_        temp file name holding data to filter
  2340.      * _RECIPIENTS_        space delimited list of recipients
  2341.      * _DATAFILE_        file for filter invocations to keep state
  2342.      */
  2343.     rlp = strstr(filter, "_RECIPIENTS_");
  2344.     tfp = strstr(filter, "_TMPFILE_");
  2345.     rfp = strstr(filter, "_RESULTFILE_");
  2346.     dfp = strstr(filter, "_DATAFILE_");
  2347.     skp = strstr(filter, "_PREPENDKEY_");
  2348.     if(rlp || tfp || dfp || rfp || skp){
  2349.     char *tfn = NULL, *dfn = NULL, *rfn = NULL,  *rl = NULL;
  2350.  
  2351.     if(tfp){
  2352.         tfn = temp_nam(NULL, "sf");        /* send filter file */
  2353.         if(tmpf)
  2354.           *tmpf = tfn;
  2355.     }
  2356.  
  2357.     if(dfp)
  2358.       dfn = filter_data_file(1);        /* filter data file */
  2359.  
  2360.     if(rfp){
  2361.         rfn = temp_nam(NULL, "rf");        /* result to show the user */
  2362.         if(resultf)
  2363.           *resultf = rfn;
  2364.     }
  2365.  
  2366.     if(rlp && env){
  2367.         char *to_l = addr_list_string(env->to,
  2368.                       simple_addr_string, 0),
  2369.          *cc_l = addr_list_string(env->cc,
  2370.                       simple_addr_string, 0),
  2371.          *bcc_l = addr_list_string(env->bcc,
  2372.                        simple_addr_string, 0);
  2373.  
  2374.         rl = fs_get(strlen(to_l) + strlen(cc_l) + strlen(bcc_l) + 3);
  2375.         sprintf(rl, "%s %s %s", to_l, cc_l, bcc_l);
  2376.         fs_give((void *)&to_l);
  2377.         fs_give((void *)&cc_l);
  2378.         fs_give((void *)&bcc_l);
  2379.         for(to_l = rl; *to_l; to_l++)    /* to_l overloaded! */
  2380.           if(*to_l == ',')            /* space delim'd list */
  2381.         *to_l = ' ';
  2382.     }
  2383.  
  2384.     cmd = (char *)fs_get(strlen(filter) + 1
  2385.                  + ((tfn) ? strlen(tfn) : 0)
  2386.                  + ((rl) ? strlen(rl) : 0)
  2387.                  + ((rfn) ? strlen(rfn) : 0)
  2388.                  + ((dfn) ? strlen(dfn) : 0));
  2389.     strcpy(cmd, filter);
  2390.     if(rlp){
  2391.         rplstr(cmd + (rlp - filter), 12, rl ? rl : "");
  2392.         if(rl)
  2393.           fs_give((void *)&rl);
  2394.     }
  2395.  
  2396.     if(skp = strstr(cmd, "_PREPENDKEY_")){
  2397.         rplstr(skp, 12, "");
  2398.         if(key)
  2399.           *key = 1;
  2400.     }
  2401.  
  2402.     if(rfp = strstr(cmd, "_RESULTFILE_")){
  2403.         if(!rfn){
  2404.         rplstr(rfp, 12, "");        /* couldn't create it! */
  2405.         dprint(1, (debugfile, "FAILED creat of _RESULTFILE_\n"));
  2406.         }
  2407.         else
  2408.           rplstr(rfp, 12, rfn);
  2409.     }
  2410.  
  2411.     if(dfp = strstr(cmd, "_DATAFILE_")){
  2412.         if(!dfn){
  2413.         rplstr(dfp, 10, "");        /* couldn't create it! */
  2414.         dprint(1, (debugfile, "FAILED creat of _DATAFILE_\n"));
  2415.         }
  2416.         else
  2417.           rplstr(dfp, 10, dfn);
  2418.     }
  2419.  
  2420.     if(tfp = strstr(cmd, "_TMPFILE_"))
  2421.       rplstr(tfp, 9, tfn);
  2422.     }
  2423.     else
  2424.       cmd = cpystr(filter);
  2425.  
  2426.     return(cmd);
  2427. }
  2428.  
  2429.  
  2430.  
  2431.  
  2432. /*
  2433.  * filter_data_file - function to return filename of scratch file for
  2434.  *              display and sending filters.  This file is created
  2435.  *              the first time it's needed, and persists until pine
  2436.  *              exits.
  2437.  */
  2438. char *
  2439. filter_data_file(create_it)
  2440.     int create_it;
  2441. {
  2442.     static char *fn = NULL;
  2443.  
  2444.     if(!fn && create_it){
  2445.     if(fn = temp_nam(NULL, "df"))
  2446.       if(creat(fn, S_IREAD|S_IWRITE) < 0)    /* owner read/write */
  2447.         fs_give((void **)&fn);
  2448.     }
  2449.     
  2450.     return(fn);
  2451. }
  2452.  
  2453.  
  2454. /*
  2455.  * filter_session_key - function to return randomly generated number
  2456.  *            representing a key for this session.  The idea is
  2457.  *            the display/sending filter could use it to muddle
  2458.  *            up any pass phrase or such stored in the
  2459.  *            "_DATAFILE_".
  2460.  */
  2461. char *
  2462. filter_session_key()
  2463. {
  2464.     static char *key = NULL;
  2465.  
  2466.     if(!key){
  2467.     sprintf(tmp_20k_buf, "%ld", random());
  2468.     key = cpystr(tmp_20k_buf);
  2469.     }
  2470.     
  2471.     return(key);
  2472. }
  2473.